组件化的应用背景和优势在此不再赘述,下面我们将从实践的角度,讨论一下如何应用组件化的思想,下面将以我自己的理解逐步展开,抛砖引玉。
哪些内容需要组件化
在我的理解中,一个项目可以拆分为以下几种组件:
- 基础组件;
- 功能组件;
- 业务组件;
下面依次来解释几种组件的定义和规则。
基础组件
- 基本配置
- 常量;
- 宏定义;
- 分类
- 各种系统类的扩展;
- 网络
- 对 AFN 的封装;
- 对 SDWebImage 的封装;
- 工具类
- 文件处理;
- 设备信息;
- 时间日期处理;
基础组件的含义就是最基础的东西,每个业务组件都有可能会使用到,基础组件需要抽取的应该是类似上面的代码,举例来说,比如我们定义了一个常量,表示接口的根路径:
|
|
那么这个常量在 Home,List,Detail 都有可能会被引用,因此我们将这种最底层的,最下一层的东西归类到基础组件。
又比如分类和扩展,我们给 UIView
的扩展定义一个计算属性:
|
|
可以想到,也会有很多的业务组件会使用到这个扩展。
功能组件
- 控件
- 弹幕;
- 轮播;
- 菜单;
- 瀑布流;
- 功能
- 断点续传;
- 音视频处理;
- CUPImage 封装;
功能组件分为可见和不可见两种,可见的是控件,不可见的是功能。功能组件的作用顾名思义,就是实现了一个功能。
业务组件
业务组件,也就是业务的具体实现了,比如一个 App 的骨架如下:
- 首页;
- 发现;
- 我的;
首页下又分为这样:
- 侧滑菜单;
- Banner;
- 热门;
这里的每个部分,都可以称为业务组件。
三种组件的关系
基础组件规则
基础组件和基础组件之间不应该产生依赖,比如我们使用网络请求组件,希望根路径是一个默认参数,但可以对外暴露和修改,像下面这样:
|
|
这时,NetWork
就依赖了 常量
这个基础组件,我们如果使用 NetWork
基础组件,还需要导入 常量
这个基础组件,这是不应该的。
但为了代码的简洁性,这样的封装又是必要的,那么应该怎么做呢?这个问题我们下面会讲到。
功能组件规则
功能组件和基础组件之间不应该产生依赖,比如我们做轮播图,会用到 UIView 的扩展
和 常量
,像下面这样:
|
|
其中 .width
和 SCREENWIDTH
,都在基础组件中,但基础组件中不仅仅是这些东西,如果依赖了基础组件,就需要导入基础组件中其他无用的代码,而且其他人使用轮播图组件,也需要导入基础组件。
因此,在功能组件中,不建议依赖基础组件,上面的代码应该改成这样:
|
|
或者直接复制代码,将需要的基础组件的功能,复制到功能组件当中。
同基础组件一样,功能组件和功能组件也不应该产生依赖,道理是一样的,我们使用一个功能,不应该将另一个功能也导入进来。
业务组件规则
基础组件和功能组件都是为业务服务的,因此业务组件可以依赖于基础组件和功能组件,快速的实现业务,但是业务组件和业务组件之间不应该产生依赖。
比如这样一条业务线,我们要求 发现
这个业务组件,点击一条视频,跳转到 视频播放器
:
|
|
这时 发现
就对 视频播放器
产生了依赖,如果将 发现
进行组件化进行剥离,能行吗?不行。
其实这个问题和网络请求使用默认参数封装一样,是组件与组件之间的通讯问题,当然,这个问题我们下面会讲到,现在再提一下是为了一会儿往下写的时候忘了填坑 …
每个组件存在的形式
- 组件内部;
- 组件外部;
- 组件测试;
组件内部
组件的内部应该使用设计模式划分文件夹的结构,例如 MVVM 结构:
|
|
组件外部
组件的外部应该是一个远程私有 pod
库,使用 CocoaPods 进行管理。
组件测试
单独的测试工程。
怎样集成各个组件
组件的集成应该像上面的图一样,基础组件和功能组件互不依赖,制作远程 pod
私有库,业务组件依赖于这些 pod
私有库开发,同样制作成远程 pod
私有库,壳工程依赖于 CocoaPods 管理这些私有库,完成整个项目。
当然还有另外的方式,比如将壳工程作为主工程,组件创建为子工程,这方式的缺点是子工程可以修改,缺少约束性,目录结构也比较凌乱。
还有将组件制作为 FrameWork
,壳工程中导入一个个 FrameWork
库,这种方式个人感觉比上一种好一些,但是在物理上,组件和壳还是没能做到分离。
因此,我个人还是更倾向于 pod
库的形式。
组件之间的通讯
- 对外公开 API 接口;
- 通过中间件的中转;
上面我们有两个遗留的问题,归纳为组件之间的通讯问题,下面就通过这两个问题,讨论一下组件之间的通讯。
网络请求默认参数
下面的思路就是暴露出 baseUrl
参数,通过中间件 NetWorkMW
将 NetWork
和 常量
两个基础组件组合,完成默认参数网络请求的封装。
|
|
发现跳转视频播放
这个思路是使用代理,对外暴露点击事件,通过中间件,导入 视频播放
业务组件,topVC
基础组件,完成向 视频播放
的跳转:
|
|
以上实际上是怎么样把多个组件组合使用起来,这种组合是确定的,还有一些是不确定的,例如有一个组件的状态改变了,我要让其他组件知道我的变化,但是我不知道都要告诉谁,怎么办?
眼珠一转,对外暴露状态变化,中间件在变化时发送通知。但是同时我想附带一个模型过去,通知的接收方怎样正确的使用这个模型呢?如果要使用模型,势必要和发送通知的业务组件产生耦合,怎么办?
以后再办,先埋个坑,这些场景我们会在以后再讲到。
组件分离的难点
组件分离的重点和难点也就是解耦,比如我们现在负责一个项目,其中的一个业务或者功能,希望实现组件化,但是它依赖于项目中的其他公共功能,该如何处理呢?这里提供两种思路:
- 拷代码,简单粗暴,摆脱依赖,对于一些不重要的工具方法,可以直接拷贝到内部来使用;
- 把组件依赖的代码先做一个
pod
库,然后依赖这个pod
库;
上面讲到的是代码方面的依赖,还有一种情况是功能方面的依赖,比如我们有一个菜单,这个菜单涉及到网络图片的加载,那么怎样将这个菜单进行组件化呢?
- 使用 Block 或者代理,将网络图片加载这部分的职责交给外部控制;
举例来说,像下面这样:
|
|
那么如果现在将它组件化,这个组件就要依赖于 SDWebImage
,我们应该修改成这样:
|
|
现在菜单就摆脱了对 SDWebImage
的依赖。
附加问题
以上的环节掌握了,应该可以尝试简单的组件化了,但是问题没完,还有哪些呢?
库的升级维护
随着项目的迭代,你负责的库升级了,其他的小伙伴们还在用上个版本的库,怎么办?
各种路径资源问题
我们在自己的库里使用了 imageNamed
、mainBundle
,但是小伙伴把我们的库拖过去后,这些路径和我们不是一个路径,Assets.xcassets
跟我们也不是同一个 Assets.xcassets
,怎么办?
这些问题你可以从这篇文章找到答案:你真的会用 CocoaPods 吗?